package cn.zyl.demo.common.tools;

import cn.zyl.demo.common.constant.CommonConstant;
import cn.zyl.demo.common.enums.CharsetEnum;
import cn.zyl.demo.common.enums.EPlatformEnums;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/** ProcessTool工具类说明: 创建操作系统进程
 * <p>ProcessBuilder(XXX).start()和Runtime.exec(XXX)功能相同,主要优点有:
 * <br>前者是jdk1.5后的新方式
 * <br>配置环境变量时更优雅
 * <br>对当前目录的控制也更合理
 * <br>错误流重定向特别方便
 * <br>进程控制更简洁</p>
 * @author wsz
 * @date 2020-07-24
 * @version v1.0
 */
public class ProcessTool {
    private ProcessTool(){throw new IllegalAccessError("ProcessTool");}
    private static final ProcessBuilder PROCESS_BUILDER = new ProcessBuilder();

    /**
     * 执行脚本命令
     * @param commend 命令列表
     * @return  java.lang.Process 终端进程对象
     */
    public static Process exec(List<String> commend) {
        try {
            return PROCESS_BUILDER.command(commend).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 执行脚本命令
     * @param commend 命令列表,不定项字符命令
     * @return  java.lang.Process 终端进程对象
     */
    public static Process exec(String... commend) {
        try {
            return PROCESS_BUILDER.command(commend).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取终端输出信息
     * @param process 终端进程对象
     * @return  java.lang.String
     */
    public static String getOutput(Process process) {
        if(null == process){return "";}
        //如果子进程的标准输出已使用ProcessBuilder.redirectOutput重定向，则此方法将返回一个空的输入流 。
        return getProcessOut(process,process.getInputStream());
    }

    /**
     * 获取错误信息
     * @param process 终端进程对象
     * @return  java.lang.String 终端输出结果
     */
    public static String getError(Process process) {
        //如果子进程的标准错误已被使用ProcessBuilder.redirectError或重定向ProcessBuilder.redirectErrorStream则此方法将返回一个空输入流
        if(null == process){return "";}
        return getProcessOut(process,process.getErrorStream());
    }

    /**
     * 获取终端输出信息
     * @param process 终端进程对象
     * @param processInput 连接到所述子进程process的输出的输入流
     * @return 信息
     */
    private static String getProcessOut(Process process,InputStream processInput) {
        StringBuilder sb = new StringBuilder();
        getProcessOutList(process, processInput).forEach(item -> sb.append(CommonConstant.ENTER_CHAR).append(item));
        return sb.toString();
    }
    private static List<String> getProcessOutList(Process process, InputStream processInput) {
        List<String> list = new ArrayList<>();
        if( null == process ){return list;}
        BufferedReader reader = null;
        try {
            if(SystemTool.osInfoType(EPlatformEnums.WINDOWS)){
                //window下cmd编码位gbk
                reader = new BufferedReader(new InputStreamReader(processInput, CharsetEnum.GBK.getType()));
            }else {
                //Process对象在创建的时候，如果不显式指定env，会从其父进程（JVM进程）获取环境参数,包含字符编码
                reader = new BufferedReader(new InputStreamReader(processInput));
            }
            while (reader.read() != -1){ list.add(reader.readLine()); }
        } catch (Exception e) {
            e.printStackTrace();
        }
        closeQuietly(reader);
        return list;
    }
    /**
     * 关流
     * @param reader 读取字符流
     */
    private static void closeQuietly(Reader reader) {
        try {
            if (reader != null) { reader.close(); }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    /**
     * 终止进程
     * @param process 终端进程对象
     */
    public static void destroy(Process process) {
        if (process != null) {
            process.destroyForcibly();
        }
    }
    public static void main(String[] args) {
        /*
        ProcessBuilder类用于创建操作系统进程，它提供一种启动和管理进程的方法。每个 ProcessBuilder 实例管理一个进程属性集。
        它的start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用，以利用相同的或相关的属性创建新的子进程。
        destroy()杀掉子进程。
        exitValue()返回子进程的出口值。
        InputStream getErrorStream()获得子进程的错误流。
        InputStream getInputStream()获得子进程的输入流。
        OutputStream getOutputStream()获得子进程的输出流。
        waitFor()导致当前线程等待，如果必要，一直要等到由该 Process 对象表示的进程已经终止。*/
        List<String> commend = new ArrayList<>();
        commend.add("java");
        commend.add("-version");
        Process process = exec(commend);
        System.out.println(getOutput(process));
        System.out.println(getError(process));
        destroy(process);
    }
}